package org.zoomdev.zoom.aop.modules;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.aop.AopFactory;
import org.zoomdev.zoom.aop.annotations.NoEnhance;
import org.zoomdev.zoom.aop.factory.impl.AsmClassloader;
import org.zoomdev.zoom.aop.utils.AsmUtils;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.res.ResScanner;
import org.zoomdev.zoom.common.utils.CachedClasses;
import org.zoomdev.zoom.ioc.Ioc;
import org.zoomdev.zoom.ioc.IocKey;
import org.zoomdev.zoom.ioc.IocTypeKey;
import org.zoomdev.zoom.ioc.IocUtils;
import org.zoomdev.zoom.ioc.annotations.IocBean;
import org.zoomdev.zoom.ioc.impl.factories.ModuleIocFactory;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class AsmBeanCreator {

    private static final Logger log = LoggerFactory.getLogger(AsmBeanCreator.class);

    public static void create(ModuleIocFactory moduleIocFactory, Ioc ioc, ResScanner scanner, AopFactory aop) {
        List<Class<?>> modules = moduleIocFactory.getBeanModules();
        AsmModuleContainer.setScanner(scanner);
        Map<String, MethodWithTypeConfig> descriptorMap = new HashMap<>();
        for (Class<?> moduleClass : modules) {
            String simpleName = moduleClass.getSimpleName();
            String className = moduleClass.getName();
            if (className.startsWith("org.zoomdev.zoom") && (
                    simpleName.startsWith("Core") ||
                            simpleName.startsWith("System") ||
                            simpleName.startsWith("Plugin"))
            ) {
                continue;
            }
            if (moduleClass.isAnnotationPresent(NoEnhance.class)) {
                continue;
            }
            Method[] methods = CachedClasses.getPublicMethods(moduleClass);
            descriptorMap.clear();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(IocBean.class)) {
                    continue;
                }
                if (log.isDebugEnabled()) {
                    log.debug("@IocBean 扫描对象:{}#{}", method.getDeclaringClass().getName(), method.getName());
                }
                String desc = Type.getMethodDescriptor(method);
                descriptorMap.put(method.getName() + desc, new MethodWithTypeConfig(desc, method));
            }

            if (!descriptorMap.isEmpty()) {
                //需要进行扫描
                ResScanner.ClassRes res = scanner.getClass(moduleClass.getName());
                if (res == null) {
                    throw new ZoomException("找不到" + moduleClass.getName() + "的资源文件");
                }
                try {
                    ClassReader cr = new ClassReader(res.getBytes());
                    cr.accept(new ScanModuleVisitor(descriptorMap), 0);
                } catch (IOException e) {
                    throw new ZoomException("解析类失败" + moduleClass, e);
                }

                Map<String, MethodWithTypeConfig> newConfig = new HashMap<>();
                for (Map.Entry<String, MethodWithTypeConfig> entry : descriptorMap.entrySet()) {
                    MethodWithTypeConfig config = entry.getValue();
                    Method method = config.method;
                    IocBean bean = method.getAnnotation(IocBean.class);
                    java.lang.reflect.Type returnType = IocUtils.getStandardReturnType(method);
                    IocKey key = new IocTypeKey(returnType, bean.name());
                    Map<IocKey, ModuleIocFactory.ModuleIocConstructor> configs = moduleIocFactory.getConfigs();
                    ModuleIocFactory.ModuleIocConstructor ct = configs.get(key);
                    if (ct == null) {
                        throw new ZoomException("找不到" + method + "的返回值的构造函数");
                    }
                    String type = config.getType();
                    if (type != null) {
                        String realClassName = AsmUtils.restoreClassName(type);
                        try {
                            Class<?> realType = Class.forName(realClassName, false, AsmClassloader.get());
                            //如果是方法返回值的子类
                            Class<?> methodReturnType = method.getReturnType();
                            if (log.isDebugEnabled()) {
                                log.debug("@IocBean {}#{} {} ===> {}", method.getDeclaringClass().getName(), method.getName(), methodReturnType.getName(), realClassName);
                            }
                            if (methodReturnType.isAssignableFrom(realType)) {
                                config.setRealType(realType);
                                //可能的替换点
                                newConfig.put(entry.getKey(), entry.getValue());
                                LazyEnhanceProxy proxy = new LazyEnhanceProxy(realType, aop, ioc, entry.getValue());
                                ct.setProxy(proxy);
                            }
                        } catch (ClassNotFoundException e) {
                            throw new ZoomException("找不到" + realClassName);
                        }
                    }

                }
                if (!newConfig.isEmpty()) {
                    AsmModuleContainer.register(moduleClass, newConfig);
                }
            }
        }
    }
}
